iT邦幫忙

2022 iThome 鐵人賽

DAY 22
1
Modern Web

你 React 了嗎? 30 天解鎖 React 技能系列 第 22

[DAY 22] useContext 跨組件溝通傳遞資料

  • 分享至 

  • xImage
  •  

cover

當專案越來越多 Components 時,為了使用 props,會將 props 層層傳入,但也許這個 Component 根本沒用到這個 props,卻為了讓下層使用,而不得不傳入,這很容易造成混淆,形成了 Props drilling

舉個例子:

App.js

import { useState } from "react";
import Header from "./Header";
import ProductList from "./ProductList";

const App = () => {
  const [orders, setOrders] = useState([]);

  const addOrder = (order) => {
    setOrders([...orders, order]);
  };

  return (
    <div>
      <Header orders={orders} />
      <ProductList addOrder={addOrder} />
    </div>
  );
};

export default App;

Header 組件需要傳入 orders,ProductList 組件需要傳入 addOrder,所以 state 會寫在最外層的地方,並向下傳入 props

ProductList.js

import Product from "./Product";

const ProductList = (props) => {
  const { addOrder } = props;

  const menu = [
    { id: 0, name: "雞肉鍋" },
    { id: 1, name: "豬肉鍋" },
    { id: 2, name: "牛肉鍋" },
    { id: 3, name: "海鮮鍋" },
    { id: 4, name: "泡菜鍋" }
  ];

  return (
    <ul>
      {menu.map((item) => (
        <Product key={item.id} {...item} addOrder={addOrder} />
      ))}
    </ul>
  );
};

export default ProductList;

ProductList 未使用 addOrder,但因應下層 Product 需要,再繼續往下傳遞,等於 ProductList 只是個中繼站

Product.js

const Product = (props) => {
  const { addOrder, id, name } = props;

  return (
    <li>
      <label>{name}</label>
      <button onClick={() => { addOrder(id); }}>+</button>
    </li>
  );
};

export default Product;

這時候的 Product 才接收到從 App 到 ProductList 傳遞下來的 Props

codesandbox 程式碼範例

props drilling

該怎麼解決這個問題?


認識 Context API

有的!Context API 可以解決 Props drilling 的問題!

Context API 可以「跨組件溝通傳遞資料」,讓組件可以省去組件層層傳遞的麻煩。

將 State 在最外層定義,Provider提供 Context value 給底下的組件使用,組件完全不用傳入 Props 就可以使用到 State

Context

useContext 是使用 Context API 的 Hook,幫助我們使用 Context API


useContext 使用方法

  • 創建 Context 檔案,輸出 Provider、context
  • 外層設定 state
  • 外層引入 Provider,將 state 傳入 Provider value
  • 組件使用 useContext 傳入 context,使用 state

跟著範例解說會比較清楚

codesandbox 程式碼範例

useContext

現在我們有 App、Header、ProductList、Product 組件

App 載入 Header、ProductList

Header 會依據點擊次數增加數量

ProductList 渲染 Product 組件,並將菜單傳入 Product

Product 品名 + 點擊按鈕


1. 創建 Context 檔案,輸出 Provider、context

context/index.js

import { createContext } from "react";
const context = createContext();
export const { Provider } = context;
export default context;

引入 createContext,創建 context,並輸出 Providercontext 給內外層組件來使用


2. 外層設定 state

App.js

import { useState } from "react";
import Header from "./Header";
import ProductList from "./ProductList";

const App = () => {
  const [orders, setOrders] = useState([]);

  const addOrder = (order) => {
    setOrders([...orders, order]);
  };

  return (
    <div>
      <Header />
      <ProductList />
    </div>
  );
};

export default App;

載入組件,設定好要傳入的 state


3. 外層引入 Provider,將 state 傳入 Provider value

import { useState } from "react";
import Header from "./Header";
import ProductList from "./ProductList";
import { Provider } from "./context";

const App = () => {
  const [orders, setOrders] = useState([]);

  const addOrder = (order) => {
    setOrders([...orders, order]);
  };

  const contextValue = {
    orders,
    addOrder
  };

  return (
    <div>
      <Provider value={contextValue}>
        <Header />
        <ProductList />
      </Provider>
    </div>
  );
};

export default App;

引入 Provider,將 state 包成 contextValue 傳入 Provider 的 value 屬性,如此一來,被 Provider 包住的組件都可以使用 contextValue 裡的 state


4. 組件使用 useContext 傳入 context,使用 state

components/ProductList.js

import React from "react";
import Product from "./Product";

const ProductList = () => {
  const menu = [
    { id: 0, name: "雞肉鍋" },
    { id: 1, name: "豬肉鍋" },
    { id: 2, name: "牛肉鍋" },
    { id: 3, name: "海鮮鍋" },
    { id: 4, name: "泡菜鍋" }
  ];

  return (
    <ul>
      {menu.map((item) => (
        <Product key={item.id} {...item} />
      ))}
    </ul>
  );
};

export default ProductList;

ProductList 不用傳入任何 context value

components/Product.js

import { useContext } from "react";
import context from "./context";

const Product = ({ id, name }) => {
  const { addOrder } = useContext(context);

  return (
    <li>
      <label>{name}</label>
      <button onClick={() => { addOrder(id); }}>+</button>
    </li>
  );
};

export default Product;

Product 引入 useContext,並將 context 傳入,這邊只需要按鈕的 function,所以只需要載入 context value 裡的 addOrder,就可以直接使用 addOrder

components/Header.js

import { useContext } from "react";
import context from "./context";

const Header = () => {
  const { orders } = useContext(context);

  return (
    <header>
      購物車 (${orders.length})
    </header>
  );
};

export default Header;

Header 也是同理,只需載入 context value 裡的 orders,就可以直接使用 orders


結語

Context API 幫助我們解決跨組件溝通的問題,在後面的章節會再講到,跟 Context API 有同樣功能的 「Redux Toolkit」,不過 Redux 使用上比 Context API 複雜多了,先喘口氣留到後面再講吧/images/emoticon/emoticon37.gif


本文將同步更新至我的部落格
Lala 的前端大補帖



上一篇
[DAY 21] Custom Hook - 客製你自己的 Hook
下一篇
[DAY 23] React-router-dom 路由設定教學,實現頁面跳轉(上)
系列文
你 React 了嗎? 30 天解鎖 React 技能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言